package bigdata.backend.datasource.utils;

import bigdata.backend.utils.DbType;

import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 数据库字段类型映射器
 * 支持 MySQL / Doris / PostgreSQL 之间的类型转换
 */
public class TypeMapper {

    // ========== 类型映射规则表 ==========
    // 结构: { 标准化基础类型 -> { 目标数据库 -> 映射函数 } }
    // 映射函数: (length, precScale) -> 目标类型字符串
    private static final Map<String, Map<DbType, BiFunction<Integer, String, String>>> TYPE_MAPPING = new HashMap<>();

    static {
        // 字符串类型
        putMapping("varchar", DbType.MYSQL, (len, ps) -> len != null ? "varchar(" + len + ")" : "varchar(255)");
        putMapping("varchar", DbType.DORIS, (len, ps) -> "varchar(" + Math.min(len != null ? len : 65533, 65533) + ")");
        putMapping("varchar", DbType.POSTGRESQL, (len, ps) -> len != null ? "varchar(" + len + ")" : "varchar");

        putMapping("char", DbType.MYSQL, (len, ps) -> "char(" + (len != null ? len : 1) + ")");
        putMapping("char", DbType.DORIS, (len, ps) -> "char(" + (len != null ? len : 1) + ")");
        putMapping("char", DbType.POSTGRESQL, (len, ps) -> "char(" + (len != null ? len : 1) + ")");

        // 整数类型
        putMapping("int", DbType.MYSQL, (len, ps) -> "int");
        putMapping("int", DbType.DORIS, (len, ps) -> "int");
        putMapping("int", DbType.POSTGRESQL, (len, ps) -> "int");

        putMapping("int_unsigned", DbType.MYSQL, (len, ps) -> "int unsigned");
        putMapping("int_unsigned", DbType.DORIS, (len, ps) -> "bigint"); // Doris 无 unsigned
        putMapping("int_unsigned", DbType.POSTGRESQL, (len, ps) -> "bigint");

        putMapping("bigint", DbType.MYSQL, (len, ps) -> "bigint");
        putMapping("bigint", DbType.DORIS, (len, ps) -> "bigint");
        putMapping("bigint", DbType.POSTGRESQL, (len, ps) -> "bigint");

        putMapping("bigint_unsigned", DbType.MYSQL, (len, ps) -> "bigint unsigned");
        putMapping("bigint_unsigned", DbType.DORIS, (len, ps) -> "bigint");
        putMapping("bigint_unsigned", DbType.POSTGRESQL, (len, ps) -> "numeric(20,0)");

        putMapping("smallint", DbType.MYSQL, (len, ps) -> "smallint");
        putMapping("smallint", DbType.DORIS, (len, ps) -> "smallint");
        putMapping("smallint", DbType.POSTGRESQL, (len, ps) -> "smallint");

        putMapping("tinyint", DbType.MYSQL, (len, ps) -> "tinyint(1)");
        putMapping("tinyint", DbType.DORIS, (len, ps) -> "boolean");
        putMapping("tinyint", DbType.POSTGRESQL, (len, ps) -> "boolean");

        // 浮点/定点类型
        putMapping("double", DbType.MYSQL, (len, ps) -> "double");
        putMapping("double", DbType.DORIS, (len, ps) -> "double");
        putMapping("double", DbType.POSTGRESQL, (len, ps) -> "double precision");

        putMapping("float", DbType.MYSQL, (len, ps) -> "float");
        putMapping("float", DbType.DORIS, (len, ps) -> "float");
        putMapping("float", DbType.POSTGRESQL, (len, ps) -> "real");

        putMapping("decimal", DbType.MYSQL, (len, ps) -> "decimal(" + (ps != null ? ps : "10,0") + ")");
        putMapping("decimal", DbType.DORIS, (len, ps) -> "decimal(" + (ps != null ? ps : "10,0") + ")");
        putMapping("decimal", DbType.POSTGRESQL, (len, ps) -> "numeric(" + (ps != null ? ps : "10,0") + ")");

        // 时间类型
        putMapping("timestamp", DbType.MYSQL, (len, ps) -> "timestamp");
        putMapping("timestamp", DbType.DORIS, (len, ps) -> "datetime");
        putMapping("timestamp", DbType.POSTGRESQL, (len, ps) -> "timestamp without time zone");

        putMapping("datetime", DbType.MYSQL, (len, ps) -> "datetime");
        putMapping("datetime", DbType.DORIS, (len, ps) -> "datetime");
        putMapping("datetime", DbType.POSTGRESQL, (len, ps) -> "timestamp without time zone");

        putMapping("date", DbType.MYSQL, (len, ps) -> "date");
        putMapping("date", DbType.DORIS, (len, ps) -> "date");
        putMapping("date", DbType.POSTGRESQL, (len, ps) -> "date");

        // 大对象/特殊类型
        putMapping("text", DbType.MYSQL, (len, ps) -> "text");
        putMapping("text", DbType.DORIS, (len, ps) -> "string");
        putMapping("text", DbType.POSTGRESQL, (len, ps) -> "text");

        putMapping("mediumtext", DbType.MYSQL, (len, ps) -> "mediumtext");
        putMapping("mediumtext", DbType.DORIS, (len, ps) -> "string");
        putMapping("mediumtext", DbType.POSTGRESQL, (len, ps) -> "text");

        putMapping("longtext", DbType.MYSQL, (len, ps) -> "longtext");
        putMapping("longtext", DbType.DORIS, (len, ps) -> "string");
        putMapping("longtext", DbType.POSTGRESQL, (len, ps) -> "text");

        putMapping("json", DbType.MYSQL, (len, ps) -> "json");
        putMapping("json", DbType.DORIS, (len, ps) -> "string");
        putMapping("json", DbType.POSTGRESQL, (len, ps) -> "jsonb");

        putMapping("enum", DbType.MYSQL, (len, ps) -> "varchar(255)");
        putMapping("enum", DbType.DORIS, (len, ps) -> "varchar(65533)");
        putMapping("enum", DbType.POSTGRESQL, (len, ps) -> "varchar(255)");

        putMapping("set", DbType.MYSQL, (len, ps) -> "varchar(255)");
        putMapping("set", DbType.DORIS, (len, ps) -> "varchar(65533)");
        putMapping("set", DbType.POSTGRESQL, (len, ps) -> "varchar(255)");

        putMapping("boolean", DbType.MYSQL, (len, ps) -> "tinyint(1)");
        putMapping("boolean", DbType.DORIS, (len, ps) -> "boolean");
        putMapping("boolean", DbType.POSTGRESQL, (len, ps) -> "boolean");
    }

    private static void putMapping(String baseType, DbType targetDb, BiFunction<Integer, String, String> mapper) {
        TYPE_MAPPING.computeIfAbsent(baseType, k -> new HashMap<>()).put(targetDb, mapper);
    }

    /**
     * 解析字段类型字符串，提取基础类型、长度、精度等信息
     *
     * @param colType   原始类型字符串，如 "varchar(255)", "int unsigned"
     * @param srcDbType 源数据库类型
     * @return new Object[]{baseType, length, precScale}
     */
    public static Object[] parseColumnType(String colType, DbType srcDbType) {
        if (colType == null) colType = "";
        colType = colType.trim().toLowerCase();
        srcDbType = srcDbType != null ? srcDbType : DbType.MYSQL;

        // ========== PostgreSQL 特殊处理 ==========
        if (srcDbType == DbType.POSTGRESQL) {
            // numeric / decimal
            if (colType.startsWith("numeric") || colType.startsWith("decimal")) {
                Matcher m = Pattern.compile("\\((\\d+),\\s*(\\d+)\\)").matcher(colType);
                String precScale = m.find() ? m.group(1) + "," + m.group(2) : null;
                return new Object[]{"decimal", null, precScale};
            }

            // varchar: character varying(N) 或 varchar(N)
            Matcher varcharMatch = Pattern.compile("^(?:character varying|varchar)\\s*\\((\\d+)\\)").matcher(colType);
            if (varcharMatch.find()) {
                return new Object[]{"varchar", Integer.valueOf(varcharMatch.group(1)), null};
            }
            if ("character varying".equals(colType) || "varchar".equals(colType)) {
                return new Object[]{"varchar", null, null};
            }

            // char: character(N) 或 char(N)
            Matcher charMatch = Pattern.compile("^(?:character|char)\\s*\\((\\d+)\\)").matcher(colType);
            if (charMatch.find()) {
                return new Object[]{"char", Integer.valueOf(charMatch.group(1)), null};
            }
            if ("character".equals(colType) || "char".equals(colType)) {
                return new Object[]{"char", null, null};
            }

            // 类型标准化
            Map<String, String> pgMap = new HashMap<>();
            pgMap.put("integer", "int");
            pgMap.put("bigint", "bigint");
            pgMap.put("double precision", "double");
            pgMap.put("timestamp without time zone", "timestamp");
            pgMap.put("timestamp with time zone", "timestamptz");
            pgMap.put("boolean", "boolean");
            pgMap.put("text", "text");

            String baseType = pgMap.getOrDefault(colType, colType);
            return new Object[]{baseType, null, null};
        }

        // ========== MySQL / Doris 处理 ==========
        Integer length = null;
        String precScale = null;
        String basePart;

        if (colType.contains("(")) {
            String[] parts = colType.split("\\(", 2);
            basePart = parts[0].trim();
            String inner = parts[1].replaceAll("\\)$", "").trim();

            if (inner.contains(",")) {
                precScale = inner; // e.g., "10,2"
            } else {
                try {
                    length = Integer.valueOf(inner);
                } catch (NumberFormatException ignored) {
                }
            }
        } else {
            basePart = colType;
        }

        // 提取基础类型（第一个词）
        String baseType = basePart.split("\\s+")[0];

        // 处理 unsigned
        if (srcDbType == DbType.MYSQL && colType.contains("unsigned")) {
            switch (baseType) {
                case "tinyint":
                    baseType = "smallint";
                    break;
                case "smallint":
                    baseType = "int";
                    break;
                case "int":
                    baseType = "int_unsigned";
                    break;
                case "bigint":
                    baseType = "bigint_unsigned";
                    break;
                case "mediumint":
                    baseType = "int";
                    break;
            }
        }

        return new Object[]{baseType, length, precScale};
    }

    /**
     * 映射字段类型
     *
     * @param srcTypeStr   源类型字符串，如 "varchar(255)"
     * @param srcDbType    源数据库类型
     * @param targetDbType 目标数据库类型
     * @return 目标类型字符串，如 "string" (Doris)
     */
    public static String mapColumnType(String srcTypeStr, DbType srcDbType, DbType targetDbType) {
        if (srcTypeStr == null || srcTypeStr.trim().isEmpty()) {
            return targetDbType == DbType.DORIS ? "string" : "text";
        }

        // 1. 解析源类型
        Object[] parsed = parseColumnType(srcTypeStr, srcDbType);
        String baseType = (String) parsed[0];
        Integer length = (Integer) parsed[1];
        String precScale = (String) parsed[2];

        // 2. 查找映射规则
        Map<DbType, BiFunction<Integer, String, String>> targetMappers = TYPE_MAPPING.get(baseType);
        if (targetMappers != null) {
            BiFunction<Integer, String, String> mapper = targetMappers.get(targetDbType);
            if (mapper != null) {
                return mapper.apply(length, precScale);
            }
        }

        // 3. 无匹配：安全兜底
        return targetDbType == DbType.DORIS ? "string" : "text";
    }
}
